# -*- coding: CP1252 -*-
from threading import Lock
from os import fstat
from time import time,strptime
from calendar import timegm
from datetime import datetime
import re

NOT_INITIALIZED = object()

class Entry(object):
    """ A cache entry, mostly an internal object. """
    def __init__(self,key):
        object.__init__(self)
        self._key=key
        self._value=NOT_INITIALIZED
        self._lock=Lock()
        return
    ## END __init__

    pass
## END class Entry

class Cache(object):
    """ An abstract, multi-threaded cache object. """
    
    def __init__(self,max_size=0):
        """ Builds a cache with a limit of max_size entries.
            If this limit is exceeded, the Least Recently Used entry is discarded.
            if max_size==0, the cache is unbounded (no LRU rule is applied).
        """
        object.__init__(self)
        self._maxsize=max_size
        self._dict={}
        self._lock=Lock()
        
        # Header of the access list
        if self._maxsize:
            self._head=Entry(None)
            self._head._previous=self._head
            self._head._next=self._head

    def __setitem__(self,name,value):
        """ Populates the cache with a given name and value. """
        self._lock.acquire()
        try:
            key = self.key(name)
            entry = self._dict.get(key)
            if not entry:
                entry = Entry(key)
                self._pack(entry,value)
                self._dict[key]=entry
                if self._maxsize:
                    entry._next = entry._previous = None
                    self._access(entry)
                    self._checklru()
            else:
                entry._lock.acquire()
                try:
                    self._pack(entry,value)
                    if self._maxsize:
                        self._access(entry)
                finally:
                    entry._lock.release()
            self.commit()
        finally:
            self._lock.release()

    def __getitem__(self,name):
        """ Gets a value from the cache, builds it if required. """
        self._lock.acquire()
        try:
            key = self.key(name)
            entry = self._dict.get(key)
            if not entry:
                entry = Entry(key)
                self._dict[key]=entry
                if self._maxsize:
                    entry._next = entry._previous = None
                    self._access(entry)
                    self._checklru()
            elif self._maxsize:
                self._access(entry)
        finally:
            self._lock.release()

        entry._lock.acquire()
        try:
            value = self._unpack(entry)
            if value is NOT_INITIALIZED:
                opened = self.check(name,entry)
                value = self.build(name,opened,entry)
                self._pack(entry,value)
                self.commit()
            else:
                opened = self.check(name,entry)
                if opened is not None:
                    value = self.build(name,opened,entry)
                    self._pack(entry,value)
                    self.commit()
            return value
        finally:
            entry._lock.release()

    def __delitem__(self,key):
        self._lock.acquire()
        try:
            key = self.key(key)
            del self._dict[key]
        finally:
            self._lock.release()

    def mru(self):
        """ Returns the Most Recently Used key """
        if self._maxsize:
            self._lock.acquire()
            try:
                return self._head._previous._key
            finally:
                self._lock.release()
        else:
            return None

    def lru(self):
        """ Returns the Least Recently Used key """
        if self._maxsize:
            self._lock.acquire()
            try:
                return self._head._next._key
            finally:
                self._lock.release()
        else:
            return None

    def key(self,name):
        """ Override this method to extract a key from the name passed to the [] operator """
        return name

    def commit(self):
        """ Override this method if you want to do something each time the underlying dictionary is modified (e.g. make it persistent). """
        pass

    def clear(self):
        """ Clears the cache """
        self._lock.acquire()
        try:
            self._dict.clear()
            if self._maxsize:
                self._head._next=self._head
                self._head._previous=self._head
        finally:
            self._lock.release()

    def check(self,name,entry):
        """ Override this method to check whether the entry with the given name is stale. Return None if it is fresh
            or an opened resource if it is stale. The object returned will be passed to the 'build' method as the 'opened' parameter.
            Use the 'entry' parameter to store meta-data if required. Don't worry about multiple threads accessing the same name,
            as this method is properly isolated.
        """
        return None

    def build(self,name,opened,entry):
        """ Build the cached value with the given name from the given opened resource. Use entry to obtain or store meta-data if needed.
             Don't worry about multiple threads accessing the same name, as this method is properly isolated.
        """
        raise NotImplementedError()
           
    def _access(self,entry):
        " Internal use only, must be invoked within a cache lock. Updates the access list. """
        if entry._next is not self._head:
            if entry._previous is not None:
                # remove the entry from the access list
                entry._previous._next=entry._next
                entry._next._previous=entry._previous
            # insert the entry at the end of the access list
            entry._previous=self._head._previous
            entry._previous._next=entry
            entry._next=self._head
            entry._next._previous=entry
            if self._head._next is self._head:
                self._head._next=entry

    def _checklru(self):
        " Internal use only, must be invoked within a cache lock. Removes the LRU entry if needed. """
        if len(self._dict)>self._maxsize:
            lru=self._head._next
            lru._previous._next=lru._next
            lru._next._previous=lru._previous
            del self._dict[lru._key]

    def _pack(self,entry,value):
        """ Store the value in the entry. """
        entry._value=value

    def _unpack(self,entry):
        """ Recover the value from the entry, returns NOT_INITIALIZED if it is not OK. """
        return entry._value

    pass
## END class Cache

    
